﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;

namespace SimJsonRpc
{
    public class DefaultRpcInvoker : IRpcInvoker
    {
        public JObject InvokeRequestAsync(JObject request, IServiceProvider serviceProvider)
        {
            if (!request.ContainsKey("id")) return null;
            if (!request.ContainsKey("method") || !request.ContainsKey("params"))
            {
                return CreateErrorResponse(request["id"].ToString(), -32600, "Invalid Request");
            }
            object result = null;
            try
            {
                string method = request["method"].ToString();
                JArray _params = JArray.FromObject(request["params"]);
                if (result == null)
                    result = Process(serviceProvider, method, _params);
            }
            catch (Exception ex)
            {
                return CreateErrorResponse(request["id"].ToString(), ex.HResult, ex.Message);
            }
            JObject response = CreateResponse(request["id"].ToString());
            response["result"] = JToken.FromObject(result);
            return response;
        }

        public object Process(IServiceProvider requestServices, string method, JArray _params)
        {
            var rpcMethod = GetMatchingMethod(requestServices, method, _params);
            if (null != rpcMethod)
            {
                var service = requestServices.GetService(rpcMethod.serviceType);
                if (null != service)
                {
                    return rpcMethod.Method.Invoke(service, rpcMethod.ConvertedParameters);
                }
                else
                {
                    throw new RpcException(500, "服务未注册");
                }
            }
            else
            {
                throw new RpcException(-32601, "Method not found");
            }
        }

        private JObject CreateResponse(string id)
        {
            JObject response = new JObject();
            response["jsonrpc"] = "2.0";
            response["id"] = id;
            return response;
        }

        private JObject CreateErrorResponse(string id, int code, string message, JObject data = null)
        {
            JObject response = CreateResponse(id);
            JObject responseError = new JObject();
            responseError["code"] = code;
            responseError["message"] = message;
            if (data != null)
                responseError["data"] = data;
            response["error"] = responseError;
            return response;
        }

        #region GetMethodInfo
        private RpcMethodInfo GetMatchingMethod(IServiceProvider requestServices, string methodName, JArray _params)
        {
            List<MethodInfo> methodsWithSameName = GetMatchingMethods(requestServices, methodName, _params);
            var potentialMatches = new List<RpcMethodInfo>();
            foreach (MethodInfo method in methodsWithSameName)
            {
                if (HasParameterSignature(method, _params, out RpcMethodInfo rpcMethodInfo))
                {
                    potentialMatches.Add(rpcMethodInfo);
                }
            }
            RpcMethodInfo rpcMethod = null;

            if (potentialMatches.Count == 1)
            {
                rpcMethod = potentialMatches.First();
            }

            //if (rpcMethod == null)
            //{
            //    var methodInfoList = new List<string>();
            //    foreach (MethodInfo matchedMethod in methodsWithSameName)
            //    {
            //        var parameterTypeList = new List<string>();
            //        foreach (ParameterInfo parameterInfo in matchedMethod.GetParameters())
            //        {
            //            string parameterType = parameterInfo.Name + ": " + parameterInfo.ParameterType.Name;
            //            if (parameterInfo.IsOptional)
            //            {
            //                parameterType += "(Optional)";
            //            }
            //            parameterTypeList.Add(parameterType);
            //        }
            //        string parameterString = string.Join(", ", parameterTypeList);
            //        methodInfoList.Add($"{{Name: '{matchedMethod.Name}', Parameters: [{parameterString}]}}");
            //    }
            //    throw new RpcException(500, string.Join(", ", methodInfoList));
            //}

            return rpcMethod;
        }
        private bool HasParameterSignature(MethodInfo method, JArray _params, out RpcMethodInfo rpcMethodInfo)
        {
            ParameterInfo[] parameterInfoList = method.GetParameters();
            if (_params.Count > parameterInfoList.Length)
            {
                rpcMethodInfo = null;
                throw new RpcException(500, "传递参数个数大于方法定义的个数");
            }
            object[] correctedParameterList = new object[parameterInfoList.Length];

            for (int i = 0; i < _params.Count; i++)
            {
                ParameterInfo parameterInfo = parameterInfoList[i];
                bool isMatch = ParameterMatches(parameterInfo, _params[i], out object convertedParameter);
                if (!isMatch)
                {
                    rpcMethodInfo = null;
                    return false;
                }
                correctedParameterList[i] = convertedParameter;
            }
            if (_params.Count < parameterInfoList.Length)
            {
                //make a new array at the same length with padded 'missing' parameters (if optional)
                for (int i = _params.Count; i < parameterInfoList.Length; i++)
                {
                    if (!parameterInfoList[i].IsOptional)
                    {
                        rpcMethodInfo = null;
                        throw new RpcException(500, "缺少参数");
                    }
                    correctedParameterList[i] = Type.Missing;
                }
            }
            rpcMethodInfo = new RpcMethodInfo(method, correctedParameterList, _params.ToArray().ToArray());
            return true;
        }
        private bool ParameterMatches(ParameterInfo parameterInfo, object value, out object convertedValue)
        {
            Type parameterType = parameterInfo.ParameterType;
            try
            {
                convertedValue = Convert.ChangeType(value, parameterType);
                return true;
            }
            catch (Exception ex)
            {
                convertedValue = null;
                return false;
            }
        }

        private List<MethodInfo> GetMatchingMethods(IServiceProvider requestServices, string methodName, JArray _params)
        {
            var serviceImplList = requestServices.GetServices<IService>();
            List<MethodInfo> allMethods = new List<MethodInfo>();
            foreach (var service in serviceImplList)
            {
                var interfaceType = service.GetType().GetInterfaces()[0];
                MethodInfo[] methods = interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
                allMethods.AddRange(methods);
            }
            List<MethodInfo> methodsWithSameName = new List<MethodInfo>();
            foreach (var method in allMethods)
            {
                RpcMethodAttribute rpcMethodAttr = (RpcMethodAttribute)method.GetCustomAttribute(typeof(RpcMethodAttribute));
                if (null != rpcMethodAttr)
                {
                    if (string.Equals(rpcMethodAttr.Name, methodName, StringComparison.OrdinalIgnoreCase))
                    {
                        methodsWithSameName.Add(method);
                    }
                }
            }
            return methodsWithSameName;
        }
        #endregion
    }
}
